// Copyright(c) 2016 Ruoyu Fan (Windy Darian), Xueyin Wan
// MIT License.

#include "ShowBase.h"

#include "renderer/Renderer.h"

#if defined(WIN32)
#	define VK_USE_PLATFORM_WIN32_KHR
#   define GLFW_EXPOSE_NATIVE_WIN32
#elif defined(__linux__)
#   define VK_USE_PLATFORM_XCB_KHR
#   define GLFW_EXPOSE_NATIVE_X11
#endif

#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <chrono>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/ext.hpp>

constexpr int WINDOW_WIDTH = 800;
constexpr int WINDOW_HEIGHT = 600;
constexpr bool WINDOW_RESIZABLE = true;
constexpr float MIN_DELTA_TIME = 1.0f / 60.0f;

class _ShowBase_Impl
{

public:
    _ShowBase_Impl():window(createWindow()),renderer(window){};

    ~_ShowBase_Impl()
    {
        glfwSetFramebufferSizeCallback(window, nullptr);
        glfwSetCursorPosCallback(window, nullptr);
        glfwSetMouseButtonCallback(window, nullptr);
    }

    void run()
    {
        mainLoop();
    }
private:
	GLFWwindow* window;
	Renderer renderer;
	// for measurement
	float total_time_past = 0.0f;
	int total_frames = 0;

	bool lmb_down = false;
	bool rmb_down = false;
	glm::vec2 cursor_pos = { 0.0f, 0.0f };
	glm::vec2 prev_cursor_pos = { 0.0f, 0.0f };
	glm::vec2 framebuffer_size = { WINDOW_WIDTH, WINDOW_HEIGHT };


	GLFWwindow* createWindow()
	{
		glfwInit();

		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // no OpenGL context

		if (WINDOW_RESIZABLE)
		{
			glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
		}
		else
		{
			glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
		}

		auto window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "VFPR - Vulkan Forward+ Renderer", nullptr, nullptr);
		
		glfwSetWindowUserPointer(window, this);
	
		auto buffersize_callback = [](GLFWwindow* window, int width, int height)
		{
			auto app = reinterpret_cast<_ShowBase_Impl*>(glfwGetWindowUserPointer(window));
			app->onResize(width, height);
		};

		auto cursorpos_callback = [](GLFWwindow* window, double xPos, double yPos)
		{
			auto app = reinterpret_cast<_ShowBase_Impl*>(glfwGetWindowUserPointer(window));
			app->onCursorPosChanged(xPos, yPos);
		};

		auto mousebutton_callback = [](GLFWwindow* window, int button, int action, int mods)
		{
			auto app = reinterpret_cast<_ShowBase_Impl*>(glfwGetWindowUserPointer(window));
			app->onMouseButton(button, action, mods);
		};

		auto key_callback = [](GLFWwindow* window, int key, int scancode, int action, int mods)
		{
			auto app = reinterpret_cast<_ShowBase_Impl*>(glfwGetWindowUserPointer(window));
			app->onKeyPress(key, scancode, action, mods);
		};

		glfwSetFramebufferSizeCallback(window, buffersize_callback);
		glfwSetCursorPosCallback(window, cursorpos_callback);
		glfwSetMouseButtonCallback(window, mousebutton_callback);

		int w, h;
		glfwGetFramebufferSize(window, &w, &h);
		framebuffer_size = { w, h };

		glfwSetKeyCallback(window, key_callback);
		//glfwSetScrollCallback(window, scrollCallback);

		return window;
	}

	void tick(float delta_time)
	{
		if (rmb_down)
		{

		}
        prev_cursor_pos = cursor_pos;
	}

	void mainLoop()
	{
		auto start_time = std::chrono::high_resolution_clock::now();
		auto previous = std::chrono::high_resolution_clock::now();
		decltype(previous) current;
		float delta_time;

		while (!glfwWindowShouldClose(window))
		{
			current = std::chrono::high_resolution_clock::now();
			delta_time = std::chrono::duration<float>(current - previous).count();

			glfwPollEvents();

			if (delta_time >= MIN_DELTA_TIME) //prevent underflow
			{
				tick(delta_time);
				previous = current;
			}

			renderer.requestDraw(delta_time);
			total_frames++;

		}
		auto end_time = std::chrono::high_resolution_clock::now();
		total_time_past = std::chrono::duration<float>(end_time - start_time).count();
		if (total_time_past > 0)
		{
			std::cout << "FPS: " << total_frames / total_time_past << std::endl;
		}
		renderer.cleanUp();
	}


	void onResize(int width, int height)
	{
		if (width == 0 || height == 0) return;
		renderer.resize(width, height);
		framebuffer_size = { width, height };
	}

	void onMouseButton(int button, int action, int mods)
	{
		if (action == GLFW_PRESS) {
			double x, y;
			glfwGetCursorPos(window, &x, &y);
			cursor_pos = {x, y};
			prev_cursor_pos = {x, y};

			if (button == GLFW_MOUSE_BUTTON_LEFT)
			{
				lmb_down = true;
			}
			else if (button == GLFW_MOUSE_BUTTON_RIGHT)
			{
				rmb_down = true;
				glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
			}
		}
		else if (action == GLFW_RELEASE)
		{
			if (button == GLFW_MOUSE_BUTTON_LEFT)
			{
				lmb_down = false;
			}
			else if (button == GLFW_MOUSE_BUTTON_RIGHT)
			{
				rmb_down = false;
				glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
			}
		}
	}

	void onKeyPress(int key, int scancode, int action, int mods)
	{
		if (action == GLFW_PRESS) 
		{
			switch (key) 
			{
			}
		}
		else if (action == GLFW_RELEASE) 
		{
			switch (key) 
			{
			}
		}
	}

	void onCursorPosChanged(double xPos, double yPos) 
	{
		//std::cout << xPos << "," << yPos << std::endl;
		if (!lmb_down && !rmb_down) {
			return;
		}
        cursor_pos = { xPos, yPos };
	}

};

void ShowBase::run()
{
	p_impl->run();
}

ShowBase::ShowBase()
	: p_impl(std::make_unique<_ShowBase_Impl>())
{}

ShowBase::~ShowBase() = default; 
